﻿using LightCAD.Core;
using LightCAD.Core.Elements;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using LightCAD.MathLib;


namespace LightCAD.Drawing
{

    public class SnapRefCurve
    {
        public SnapPointType Type { get; set; }
        public Curve2d Curve { get; set; }

        public SnapRefCurve(SnapPointType type, Curve2d curve)
        {
            Type = type;
            Curve = curve;
        }
    }

    public class SnapPointResult
    {
        public LcElement Element { get; set; }

        public Vector2 Point { get; set; }
        /// <summary>
        /// 相关联的元素
        /// </summary>
        public List<SnapRefCurve> Curves { get; } = new List<SnapRefCurve>();

        public string Name { get; set; }
        public bool IsTemp { get; set; } = true;
    }

    public class SnapResult
    {
        public Vector2 MousePoint { get; set; }
        public Vector2 SnapPoint { get; set; }
        public Vector2 PrePoint { get; set; }
        public List<SnapRefCurve> Curves { get; } = new List<SnapRefCurve>();
        public string ExtType { get; set; }

        internal void SetSnap(SnapPointResult result)
        {
            this.SnapPoint = result.Point;
            this.Curves.AddRange(result.Curves);
        }

        internal void AddSnap(SnapPointResult result)
        {
            this.Curves.AddRange(result.Curves);
        }
    }
    public class SnapRuntime
    {
        private ViewportRuntime vprotRt;
        private Task houverTask;
        private CancellationTokenSource houverTaskCTS;
        private Task moveTask;
        private CancellationTokenSource moveTaskCTS;

        public SnapResult Current { get; set; }
        /// <summary>
        /// 捕捉参考点
        /// </summary>
        public List<SnapPointResult> RefPoints { get; } = new List<SnapPointResult>();
        /// <summary>
        /// 捕捉参考线，靠近悬停后选中，再悬停取消
        /// </summary>
        public List<Curve2d> RefCurves { get; } = new List<Curve2d>();
        public SnapRuntime(ViewportRuntime vportRt)
        {
            this.vprotRt = vportRt;
        }

        public void MouseHover(Vector2 mousePoint)
        {
            if (this.vprotRt == null)
            {
                return;
            }
            if (houverTask != null && houverTaskCTS != null)
            {
                houverTaskCTS.Cancel();
                houverTask = null;
                houverTaskCTS = null;
            }

            houverTaskCTS = new CancellationTokenSource();
            houverTask = new Task(DoHoverTask, mousePoint, houverTaskCTS.Token);
            houverTask.Start();
        }
        public void MouseMove(Vector2 mousePoint, Vector2 prePoint = null)
        {
            if (this.vprotRt == null)
            {
                return;
            }

            if (moveTask != null && moveTaskCTS != null)
            {
                moveTaskCTS.Cancel();
                moveTask = null;
                moveTaskCTS = null;
            }

            moveTaskCTS = new CancellationTokenSource();

            moveTask = new(() => { DoMoveTask(mousePoint, prePoint); });
            //moveTask = new Task(DoMoveTask, mousePoint, );
            moveTask.Start();

        }
        public void SnapHorzVert(Vector2 prePoint, Vector2 mousePoint)
        {
            var result = new SnapResult();
            if (prePoint == null)
                return;
            result.MousePoint = mousePoint;
            result.PrePoint = prePoint;
            Line2d Ytop = new Line2d(prePoint, new Vector2(prePoint.X, prePoint.Y + 5000000));
            Line2d YBootom = new Line2d(prePoint, new Vector2(prePoint.X, prePoint.Y - 5000000));
            Line2d Xleft = new Line2d(prePoint, new Vector2(prePoint.X - 5000000, prePoint.Y));
            Line2d XRight = new Line2d(prePoint, new Vector2(prePoint.X + 5000000, prePoint.Y));
            var ErrValue = 200;
            var distance = GeoUtils.PointDistanceToLine(mousePoint, Ytop.Start, Ytop.End);

            if (Math.Abs(distance) < ErrValue && Ytop.Start.Y < mousePoint.Y)
            {

                //Y轴向上
                GeoUtils.PointToLineDistance(Ytop.Start, Ytop.End, mousePoint, out Vector2 prjPoint, out double d);
                result.SnapPoint = prjPoint;
                goto Ent;
            }

            distance = GeoUtils.PointDistanceToLine(mousePoint, YBootom.Start, YBootom.End);

            if (Math.Abs(distance) < ErrValue && YBootom.Start.Y > mousePoint.Y)
            {
                //Y轴向下
                GeoUtils.PointToLineDistance(YBootom.Start, YBootom.End, mousePoint, out Vector2 prjPoint, out double d);
                result.SnapPoint = prjPoint;
                goto Ent;
            }
            distance = GeoUtils.PointDistanceToLine(mousePoint, Xleft.Start, Xleft.End);

            if (Math.Abs(distance) < ErrValue && Xleft.Start.X > mousePoint.X)
            {
                //X轴向左
                GeoUtils.PointToLineDistance(Xleft.Start, Xleft.End, mousePoint, out Vector2 prjPoint, out double d);
                result.SnapPoint = prjPoint;
                goto Ent;
            }

            distance = GeoUtils.PointDistanceToLine(mousePoint, XRight.Start, XRight.End);

            if (Math.Abs(distance) < ErrValue && XRight.Start.X < mousePoint.X)
            {
                //X轴向右
                GeoUtils.PointToLineDistance(XRight.Start, XRight.End, mousePoint, out Vector2 prjPoint, out double d);
                result.SnapPoint = prjPoint;
                goto Ent;
            }


        Ent:
            if (result.SnapPoint != null && result.PrePoint != null)
            {
                result.ExtType = "HorzVert";
                var refPoint = new SnapPointResult();
                refPoint.Point = result.SnapPoint;
                Current = result;
            }
            else
                this.Current = null;

            moveTask = null;
            moveTaskCTS = null;
        }
        private void DoHoverTask(object state)
        {

            var mousePoint = (Vector2)state;
            var elements = this.vprotRt.GetElementsInBounds(houverTaskCTS);
            var news = new List<SnapPointResult>();
            foreach (var element in elements)
            {
                if (houverTaskCTS != null && houverTaskCTS.IsCancellationRequested) break;

                var eleAction = (element.RtAction as ElementAction);
                var snapEleResult = eleAction.SetViewport(this.vprotRt).SnapPoint(this, element, mousePoint, true);
                if (snapEleResult != null)
                {
                    var refPoint = RefPoints.FirstOrDefault((item) => item.Point == snapEleResult.Point);
                    if (refPoint != null)
                    {
                        if (!refPoint.IsTemp)//不是临时参考点，再次Hover就移除
                        {
                            RefPoints.Remove(refPoint);
                        }
                        else
                        {
                            refPoint.Curves.AddRange(snapEleResult.Curves);
                        }
                    }
                    else
                    {
                        refPoint = new SnapPointResult();
                        refPoint.Point = snapEleResult.Point;
                        refPoint.Curves.AddRange(snapEleResult.Curves);
                        RefPoints.Add(refPoint);
                        news.Add(refPoint);
                    }
                }
            }

            //foreach(var rp in news)
            //{
            //    rp.IsTemp = false;
            //}
            houverTask = null;
            houverTaskCTS = null;
        }

        private void DoMoveTask(object state, Vector2 prePoint)
        {
            var mousePoint = (Vector2)state;
            var elements = this.vprotRt.GetElementsInBounds(moveTaskCTS);
            var result = new SnapResult();
            result.MousePoint = mousePoint;

            foreach (var element in elements)
            {
                if (moveTaskCTS != null && moveTaskCTS.IsCancellationRequested) break;

                var eleAction = (element.RtAction as ElementAction);

                List<Vector2> crossVectorPoints = new List<Vector2>();
                foreach (var element1 in elements)
                {
                    if (element1 == element)
                    {
                        continue;
                    }
                    List<LcLine> line2Ds = new List<LcLine>();
                    if (element is LcLine)
                    {
                        line2Ds.Add((element as LcLine));
                    }
                    else
                    if (element is LcAxisLine)
                    {
                        line2Ds.Add((element as LcAxisLine).AxisLine);
                    }
                    if (element is LcAxisGrid)
                    {
                        LcAxisGrid lcAxisGrid = (element as LcAxisGrid);
                        foreach (LcAxisLine lcAxisLine in lcAxisGrid.Elements)
                        {
                            var axisLine = lcAxisLine.AxisLine;
                            line2Ds.Add(axisLine);

                        }
                    }
                    if (line2Ds.Count > 0)
                    {
                        foreach (var line2D in line2Ds)
                        {
                            //List<Vector2> vector2s = lcAxisLine.GetCrossVectorByLine(lcLine);

                            List<Vector2> vector2s = element1.GetCrossVectorByLine(line2D.Line);
                            vector2s.RemoveAll(v => v == null);
                            foreach (var vector2 in vector2s)
                            {

                                if (vector2 !=null &&!double.IsNaN(vector2.X) && !double.IsNaN(vector2.Y))
                                {
                                    crossVectorPoints.Add(vector2);
                                }
                            }
                        }
                    }
                }
                var snapEleResult = eleAction.SetViewport(this.vprotRt).SnapLine(this, crossVectorPoints, mousePoint, false);
                if (snapEleResult == null)
                    snapEleResult = eleAction.SetViewport(this.vprotRt).SnapPoint(this, element, mousePoint, false, prePoint);

                if (snapEleResult != null)
                {
                    if (result.SnapPoint == null)
                    {
                        //如果还未捕捉点，将元素结果加入集合
                        result.SetSnap(snapEleResult);
                        prePoint = null;
                    }
                    else if (snapEleResult.Point == result.SnapPoint)
                    {
                        result.AddSnap(snapEleResult);
                        prePoint = null;
                    }
                    else
                    {
                        //先捕捉的先作为结果，后捕捉的忽略
                    }
                }
                else
                {
                }
            }
            if (prePoint != null)
            {

                SnapHorzVert(prePoint, mousePoint);
            }
            else
            {

                if (result.SnapPoint != null)
                    this.Current = result;
                else
                    this.Current = null;
            }

            moveTask = null;
            moveTaskCTS = null;
        }

        public List<SnapPointResult> GetRefPoints(LcElement element)
        {
            var result = new List<SnapPointResult>();
            foreach (var rp in this.RefPoints)
            {
                if (element == rp.Element)
                {
                    result.Add(rp);
                }
                else
                {
                    foreach (var refCurve in rp.Curves)
                    {
                        if (refCurve.Curve.Source == element)
                        {
                            result.Add(rp);
                            break;
                        }
                    }
                }
            }
            return result;
        }
    }
}
